/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.fresco.helper.loading;

import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.render.Arc;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.Rect;
import ohos.agp.utils.TextAlignment;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayAttributes;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.app.Environment;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.global.resource.NotExistException;
import ohos.global.resource.RawFileEntry;
import ohos.global.resource.Resource;
import ohos.global.resource.ResourceManager;
import ohos.utils.LruBuffer;

import com.facebook.fresco.helper.ResourceTable;
import com.facebook.fresco.helper.utils.AttrUtil;
import com.facebook.fresco.helper.utils.MLog;
import com.oszc.bbhmlibrary.wrapper.RectF;
import com.oszc.bbhmlibrary.wrapper.TextUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Locale;
import java.util.Optional;

/**
 * 加载进度条观点
 *
 * @author dev
 * @since 2021-08-02
 */
public class LoadingProgressBarComponent extends Component implements Component.DrawTask {

    /**
     * 填充型径向
     */
    public static final int FILL_TYPE_RADIAL = 0;
    /**
     * 填充型中心
     */
    public static final int FILL_TYPE_CENTER = 1;
    /**
     * 媒体动画速度
     */
    public static final int MEDIUM_ANIMATION_SPEED = 25;
    private static final int MEASURED_STATE_TOO_SMALL = 0x01000000;
    private static final int MEASURED_STATE_MASK = 0xff000000;
    private static final int MEASURED_SIZE_MASK = 0x00ffffff;
    private static final int DEFAULT_MAX = 100;
    private static final int DEFAULT_PROGRESS = 0;
    private static final int DEFAULT_START_ANGLE = -90;
    private static final float DEFAULT_STROKE_WIDTH = 3f;
    private static final float DEFAULT_TEXT_SIZE = 14f;
    private static final int DEFAULT_VIEW_SIZE = 96;
    private static LruBuffer<String, Font> sTypefaceCache = new LruBuffer(8);
    private OnProgressListener mListener;
    private DisplayAttributes mDisplayMetrics;
    private int mMax = DEFAULT_MAX;
    private int mProgress = DEFAULT_PROGRESS;
    private int mStartAngle = DEFAULT_START_ANGLE;
    private boolean mInverted = false;
    private boolean mCounterclockwise = false;
    private boolean mShowStroke = true;
    private float mStrokeWidth = DEFAULT_STROKE_WIDTH;
    private boolean mShowText = true;
    private float mTextSize = DEFAULT_TEXT_SIZE;
    private String mText;
    private String mTypeface;
    private boolean mShowImage = true;
    private Element mImage;
    private Rect mImageRect;
    private Paint mStrokePaint;
    private Paint mTextPaint;
    private Paint mProgressPaint;
    private Paint mBackgroundPaint;
    private RectF mInnerRectF;
    private int mProgressFillType = FILL_TYPE_RADIAL;
    private int mAnimationSpeed = MEDIUM_ANIMATION_SPEED;
    private AnimationHandler mAnimationHandler = new AnimationHandler(EventRunner.getMainEventRunner());
    private int mViewSize;

    /**
     * 加载进度条观点
     *
     * @param context 上下文
     */
    public LoadingProgressBarComponent(Context context) {
        this(context, (AttrSet) null);
    }

    /**
     * 加载进度条观点
     *
     * @param context 上下文
     * @param attrSet attr集
     */
    public LoadingProgressBarComponent(Context context, AttrSet attrSet) {
        this(context, attrSet, "");
    }

    /**
     * 加载进度条观点
     *
     * @param context 上下文
     * @param attrSet attr集
     * @param styleName 风格的名称
     */
    public LoadingProgressBarComponent(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(context, attrSet);
        mViewSize = (int) Math.min(DEFAULT_VIEW_SIZE * 1.7f, getWidth());
        addDrawTask(this);
    }

    private void init(Context context, AttrSet attrs) {
        if (attrs != null) {
            Optional<Display> display = DisplayManager.getInstance().getDefaultDisplay(context);
            Point pt = new Point();
            display.get().getSize(pt);
            mDisplayMetrics = display.get().getAttributes();

            mStrokeWidth = mStrokeWidth * mDisplayMetrics.densityPixels;
            mTextSize = mTextSize * mDisplayMetrics.scalDensity;
            mMax = AttrUtil.getInt(attrs, "ffMax", mMax);
            mProgress = AttrUtil.getInt(attrs, "ffProgress", mProgress);
            mStartAngle = AttrUtil.getInt(attrs, "ffStartAngle", mStartAngle);
            mInverted = AttrUtil.getBoolean(attrs, "ffInverted", mInverted);
            mCounterclockwise = AttrUtil.getBoolean(attrs, "ffCounterclockwise", mCounterclockwise);
            mStrokeWidth = AttrUtil.getFloat(attrs, "ffStrokeWidth", mStrokeWidth);
            mTypeface = AttrUtil.getString(attrs, "ffTypeface", "");
            mTextSize = AttrUtil.getFloat(attrs, "textSize", mTextSize);
            mText = AttrUtil.getString(attrs, "text", "");
            mShowStroke = AttrUtil.getBoolean(attrs, "ffShowStroke", mShowStroke);
            mShowText = AttrUtil.getBoolean(attrs, "ffShowText", mShowText);
            mImage = AttrUtil.getElement(attrs, "ffImage", null);
            int backgroundColor = context.getColor(ResourceTable.Color_default_background_color);
            backgroundColor = AttrUtil.getIntColor(attrs, "ffBackgroundColor", backgroundColor);
            int progressColor = context.getColor(ResourceTable.Color_default_progress_color);
            progressColor = AttrUtil.getIntColor(attrs, "ffProgressColor", progressColor);
            int strokeColor = context.getColor(ResourceTable.Color_default_stroke_color);
            strokeColor = AttrUtil.getIntColor(attrs, "ffStrokeColor", strokeColor);
            int textColor = context.getColor(ResourceTable.Color_default_text_color);
            textColor = AttrUtil.getIntColor(attrs, "textColor", textColor);
            String string = AttrUtil.getString(attrs, "ffProgressFillType", "radial");
            mProgressFillType = "radial".equals(string) ? 0 : 1;

            mBackgroundPaint = new Paint();
            mBackgroundPaint.setAntiAlias(true);
            mBackgroundPaint.setColor(new Color(backgroundColor));
            mBackgroundPaint.setStyle(Paint.Style.FILL_STYLE);

            mProgressPaint = new Paint();
            mProgressPaint.setAntiAlias(true);
            mProgressPaint.setColor(new Color(progressColor));
            mProgressPaint.setStyle(Paint.Style.FILL_STYLE);

            mStrokePaint = new Paint();
            mStrokePaint.setAntiAlias(true);
            mStrokePaint.setColor(new Color(strokeColor));
            mStrokePaint.setStyle(Paint.Style.STROKE_STYLE);
            mStrokePaint.setStrokeWidth(AttrHelper.vp2px(mStrokeWidth, getContext()));

            mTextPaint = new Paint();
            mTextPaint.setAntiAlias(true);
            mTextPaint.setColor(new Color(textColor));
            mTextPaint.setTextSize(AttrHelper.fp2px(mTextSize, getContext()));
            mTextPaint.setTextAlign(TextAlignment.CENTER);

            mInnerRectF = new RectF();
            mImageRect = new Rect();
        }
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        mInnerRectF.set(0, 0, mViewSize, mViewSize);
        int xCoor = (getWidth() - mViewSize) / 2;
        int yCoor = (getHeight() - mViewSize) / 2;
        mInnerRectF.fuse(xCoor, yCoor);
        if (mShowStroke) {
            final int halfBorder = (int) (mStrokePaint.getStrokeWidth() / 2f + 0.5f);
            mInnerRectF.translate(halfBorder, halfBorder);
        }

        float centerX = mInnerRectF.getCenter().getPointX();
        float centerY = mInnerRectF.getCenter().getPointY();

        canvas.drawArc(mInnerRectF, new Arc(0, 360, true), mBackgroundPaint);
        switch (mProgressFillType) {
            case FILL_TYPE_RADIAL:
                int sweepAngle = 360 * mProgress / mMax;
                if (mInverted) {
                    sweepAngle = sweepAngle - 360;
                }
                if (mCounterclockwise) {
                    sweepAngle = -sweepAngle;
                }
                canvas.drawArc(mInnerRectF, new Arc(mStartAngle, sweepAngle, true), mProgressPaint);
                break;
            case FILL_TYPE_CENTER:
                int size = mViewSize / 2;
                float radius = size * ((float) mProgress / mMax);
                if (mShowStroke) {
                    radius = radius + 0.5f - mStrokePaint.getStrokeWidth();
                }
                canvas.drawCircle(centerX, centerY, radius, mProgressPaint);
                break;
            default:
                MLog.error("Invalid Progress Fill = " + mProgressFillType);
                break;
        }
        setTypeFace(canvas, (int) centerX, centerY);

        if (null != mImage && mShowImage) {
            int drawableSize = mImage.getWidth();
            mImageRect.set(0, 0, drawableSize, drawableSize);
            mImageRect.offset((getWidth() - drawableSize) / 2, (getHeight() - drawableSize) / 2);
            mImage.setBounds(mImageRect);
            mImage.drawToCanvas(canvas);
        }

        if (mShowStroke) {
            canvas.drawOval(mInnerRectF, mStrokePaint);
        }
    }

    /**
     * 获取最大
     *
     * @return int
     */
    public int getMax() {
        return mMax;
    }

    /**
     * 设置最大
     *
     * @param max 马克斯
     */
    public void setMax(int max) {
        if (max <= 0 || max < mProgress) {
            MLog.error(String.format("Max (%d) must be > 0 and >= %d", max, mProgress));
        }
        mMax = max;
        invalidate();
    }

    /**
     * 得到动画速度
     *
     * @return int
     */
    public int getAnimationSpeed() {
        return this.mAnimationSpeed;
    }

    /**
     * 设置动画的速度
     *
     * @param animationSpeed 动画速度
     */
    public void setAnimationSpeed(int animationSpeed) {
        this.mAnimationSpeed = animationSpeed;
    }

    /**
     * 动画进步填满
     */
    public void animateProgressFill() {
        mAnimationHandler.removeEvent(0);
        mAnimationHandler.setAnimateTo(mMax);
        mAnimationHandler.sendEvent(0);
        invalidate();
    }

    /**
     * 动画进步填满
     *
     * @param animateTo - the progress value the animation should stop at (0 - MAX)
     */
    public void animateProgressFill(int animateTo) {
        mAnimationHandler.removeEvent(0);
        if (animateTo > mMax || animateTo < 0) {
            MLog.error(String.format(Locale.ENGLISH, "Animation progress (%d) is greater than the max progress (%d) or lower than 0 ", animateTo, mMax));
        }
        mAnimationHandler.setAnimateTo(animateTo);
        mAnimationHandler.sendEvent(0);
        invalidate();
    }

    /**
     * 停止动画
     */
    public void stopAnimating() {
        mAnimationHandler.removeEvent(0);
        mAnimationHandler.setAnimateTo(mProgress);
        invalidate();
    }

    /**
     * 得到进度
     *
     * @return int
     */
    public int getProgress() {
        return mProgress;
    }

    /**
     * 设置进度
     *
     * @param progress 进步
     */
    public void setProgress(int progress) {
        if (progress > mMax || progress < 0) {
            MLog.error(String.format(Locale.ENGLISH, "Progress (%d) must be between %d and %d", progress, 0, mMax));
        }
        mProgress = progress;
        if (null != mListener) {
            if (mProgress == mMax) {
                mListener.onProgressCompleted();
            } else {
                mListener.onProgressChanged(mProgress, mMax);
            }
        }
        invalidate();
    }

    /**
     * 开始角
     *
     * @return int
     */
    public int getStartAngle() {
        return mStartAngle;
    }

    /**
     * 设置开始角
     *
     * @param startAngle start angle in degrees
     */
    public void setStartAngle(int startAngle) {
        mStartAngle = startAngle;
    }

    /**
     * 是倒
     *
     * @return boolean
     */
    public boolean isInverted() {
        return mInverted;
    }

    /**
     * 设置倒
     *
     * @param inverted 倒
     */
    public void setInverted(boolean inverted) {
        mInverted = inverted;
    }

    /**
     * 是逆时针方向
     *
     * @return boolean
     */
    public boolean isCounterclockwise() {
        return mCounterclockwise;
    }

    /**
     * 设置逆时针
     *
     * @param counterclockwise 逆时针方向
     */
    public void setCounterclockwise(boolean counterclockwise) {
        mCounterclockwise = counterclockwise;
    }

    /**
     * 得到进步的颜色
     *
     * @return int
     */
    public int getProgressColor() {
        return mProgressPaint.getColor().getValue();
    }

    /**
     * 设置进度颜色
     *
     * @param color 颜色
     */
    public void setProgressColor(int color) {
        mProgressPaint.setColor(new Color(color));
        invalidate();
    }

    /**
     * 得到背景色
     *
     * @return int
     */
    public int getBackgroundColor() {
        return mBackgroundPaint.getColor().getValue();
    }

    /**
     * 设置背景颜色
     *
     * @param color 颜色
     */
    public void setBackgroundColor(int color) {
        mBackgroundPaint.setColor(new Color(color));
        invalidate();
    }

    /**
     * 得到文本颜色
     *
     * @return int
     */
    public int getTextColor() {
        return mTextPaint.getColor().getValue();
    }


    /**
     * 设置文本颜色
     *
     * @param color 颜色
     */
    public void setTextColor(int color) {
        mTextPaint.setColor(new Color(color));
        invalidate();
    }

    /**
     * 得到文本大小
     *
     * @return float
     */
    public float getTextSize() {
        return mTextSize;
    }

    /**
     * 设置文本大小
     *
     * @param sizeSp in sp for the text
     */
    public void setTextSize(int sizeSp) {
        mTextSize = sizeSp * mDisplayMetrics.scalDensity;
        mTextPaint.setTextSize((int) mTextSize);
        invalidate();
    }

    /**
     * 得到文本
     *
     * @return {@link String}
     */
    public String getText() {
        return mText;
    }

    /**
     * 设置文本
     *
     * @param text 文本
     */
    public void setText(String text) {
        mText = text;
        invalidate();
    }

    /**
     * 获得字体
     *
     * @return {@link String}
     */
    public String getTypeface() {
        return mTypeface;
    }

    /**
     * 设置字体
     *
     * @param typeface that the text is displayed in
     */
    public void setTypeface(String typeface) {
        mTypeface = typeface;
        invalidate();
    }

    /**
     * 文本显示
     *
     * @return boolean
     */
    public boolean isTextShowing() {
        return mShowText;
    }

    /**
     * 设置显示文本
     *
     * @param showText show or hide text
     */
    public void setShowText(boolean showText) {
        mShowText = showText;
        invalidate();
    }

    /**
     * 得到描边颜色
     *
     * @return int
     */
    public int getStrokeColor() {
        return mStrokePaint.getColor().getValue();
    }

    /**
     * 设置描边颜色
     *
     * @param color - color of the stroke part of the view
     */
    public void setStrokeColor(int color) {
        mStrokePaint.setColor(new Color(color));
        invalidate();
    }

    /**
     * 得到笔划宽度
     *
     * @return float
     */
    public float getStrokeWidth() {
        return mStrokeWidth;
    }

    /**
     * 设置笔划宽度
     *
     * @param widthDp in dp for the pie border
     */
    public void setStrokeWidth(int widthDp) {
        mStrokeWidth = widthDp * mDisplayMetrics.densityPixels;
        mStrokePaint.setStrokeWidth(mStrokeWidth);
        invalidate();
    }

    /**
     * 是否描边正在显示
     *
     * @return boolean
     */
    public boolean isStrokeShowing() {
        return mShowStroke;
    }

    /**
     * 设置显示描边
     *
     * @param showStroke show or hide stroke
     */
    public void setShowStroke(boolean showStroke) {
        mShowStroke = showStroke;
        invalidate();
    }

    /**
     * 得到图像可拉的
     *
     * @return {@link Element}
     */
    public Element getImageDrawable() {
        return mImage;
    }

    /**
     * 设置图像可拉的
     *
     * @param image drawable of the view
     */
    public void setImageDrawable(Element image) {
        mImage = image;
        invalidate();
    }

    /**
     * Sets the drawable of the view.
     *
     * @param resId resource id of the view's drawable
     */
    public void setImageResource(int resId) {
        if (null != getResourceManager()) {
            Resource resource = null;
            try {
                resource = getResourceManager().getResource(resId);
                mImage = new PixelMapElement(resource);
                invalidate();
            } catch (IOException error) {
                MLog.error(" setImageResource IOException " + error.getLocalizedMessage());
            } catch (NotExistException error) {
                MLog.error(" setImageResource NotExistException " + error.getLocalizedMessage());
            }
        }
    }

    /**
     * 图像显示
     *
     * @return boolean
     */
    public boolean isImageShowing() {
        return mShowImage;
    }

    /**
     * 设置显示图片
     *
     * @param showImage show or hide image
     */
    public void setShowImage(boolean showImage) {
        mShowImage = showImage;
        invalidate();
    }

    /**
     * 得到进步填充类型
     *
     * @return int
     */
    public int getProgressFillType() {
        return mProgressFillType;
    }

    /**
     * 设置进度填写类型
     *
     * @param fillType one of {@link #FILL_TYPE_CENTER}, {@link #FILL_TYPE_RADIAL}
     */
    public void setProgressFillType(int fillType) {
        mProgressFillType = fillType;
    }

    /**
     * 设置进度监听器
     *
     * @param listener progress listener
     */
    public void setOnProgressListener(OnProgressListener listener) {
        mListener = listener;
    }

    private void setTypeFace(Canvas canvas, int centerX, float centerY) {
        if (!TextUtils.isEmpty(mText) && mShowText) {
            if (!TextUtils.isEmpty(mTypeface)) {
                Font typeface = sTypefaceCache.get(mTypeface);
                if (null == typeface && null != getResourceManager()) {
                    typeface = createFontBuild(getContext(), "");
                    sTypefaceCache.put(mTypeface, typeface);
                }
                mTextPaint.setFont(typeface);
            }
            int xpos = centerX;
            int ypos = (int) (centerY - (mTextPaint.descent() + mTextPaint.ascent()) / 2);
            canvas.drawText(mTextPaint, mText, xpos, ypos);
        }
    }


    /**
     * 创建字体构建
     *
     * @param context 上下文
     * @param name 的名字
     * @return {@link Font}
     */
    private Font createFontBuild(Context context, String name) {
        ResourceManager resManager = getResourceManager();
        RawFileEntry rawFileEntry = resManager.getRawFileEntry("resources/rawfile/" + name);
        Resource resource = null;
        try {
            resource = rawFileEntry.openRawFile();
        } catch (IOException error) {
            MLog.error(" createFontBuild IOException1 " + error.getLocalizedMessage());
        }
        StringBuffer fileName = new StringBuffer(name);
        File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName.toString());
        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file);
            int index;
            byte[] bytes = new byte[1024];
            while ((index = resource.read(bytes)) != -1) {
                outputStream.write(bytes, 0, index);
                outputStream.flush();
            }
        } catch (FileNotFoundException error) {
            MLog.error(" createFontBuild FileNotFoundException " + error.getLocalizedMessage());
        } catch (IOException error) {
            MLog.error(" createFontBuild IOException2 " + error.getLocalizedMessage());
        } finally {
            try {
                if (resource != null) {
                    resource.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException error) {
                MLog.error(" createFontBuild IOException3 " + error.getLocalizedMessage());
            }

        }
        Font.Builder builder = new Font.Builder(file);
        return builder.build();
    }

    /**
     * 进度监听器
     *
     * @author dev
     * @since 2021-08-02
     */
    public interface OnProgressListener {
        /**
         * 进步改变了
         *
         * @param progress 进步
         * @param max 马克斯
         */
        void onProgressChanged(int progress, int max);

        /**
         * 进度完成
         */
        void onProgressCompleted();
    }

    /**
     * 动画处理程序
     *
     * @author dev
     * @since 2021-08-02
     */
    private class AnimationHandler extends EventHandler {
        private int mAnimateTo;

        /**
         * 动画处理程序
         *
         * @param runner 跑步者
         */
        AnimationHandler(EventRunner runner) {
            super(runner);
        }

        /**
         * 设置动画
         *
         * @param animateTo 动画,
         */
        public void setAnimateTo(int animateTo) {
            mAnimateTo = animateTo;
        }

        @Override
        protected void processEvent(InnerEvent event) {
            super.processEvent(event);
            if (mProgress > mAnimateTo) {
                setProgress(mProgress - 1);
                sendEvent(0, mAnimationSpeed);
            } else if (mProgress < mAnimateTo) {
                setProgress(mProgress + 1);
                sendEvent(0, mAnimationSpeed);
            } else {
                removeEvent(0);
            }
        }
    }
}
